/*
 * Unidata Platform Community Edition
 * Copyright (c) 2013-2020, UNIDATA LLC, All rights reserved.
 * This file is part of the Unidata Platform Community Edition software.
 *
 * Unidata Platform Community Edition is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Unidata Platform Community Edition is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
 */
package org.unidata.mdm.rest.v1.dq.core.converter;

import java.util.stream.Collectors;

import org.unidata.mdm.dq.core.type.composite.CompositeFunctionNodeType;
import org.unidata.mdm.dq.core.type.model.source.CompositeCleanseFunctionLogic;
import org.unidata.mdm.dq.core.type.model.source.CompositeCleanseFunctionNode;
import org.unidata.mdm.dq.core.type.model.source.CompositeCleanseFunctionSource;
import org.unidata.mdm.dq.core.type.model.source.CompositeCleanseFunctionTransition;
import org.unidata.mdm.rest.v1.dq.core.ro.functions.CompositeCleanseFunctionRO;
import org.unidata.mdm.rest.v1.dq.core.ro.functions.CompositeFunctionLogicRO;
import org.unidata.mdm.rest.v1.dq.core.ro.functions.CompositeFunctionNodeRO;
import org.unidata.mdm.rest.v1.dq.core.ro.functions.CompositeFunctionTransitionRO;

/**
 * @author Mikhail Mikhailov on Feb 8, 2021
 */
public class CompositeCleanseFunctionConverter extends AbstractCleanseFunctionConverter<CompositeCleanseFunctionSource, CompositeCleanseFunctionRO> {
    /**
     * CC.
     */
    private static final ConstantConverter CONSTANT_CONVERTER = new ConstantConverter();
    /**
     * Constructor.
     * @param in
     * @param out
     */
    public CompositeCleanseFunctionConverter() {
        super(CompositeCleanseFunctionConverter::convert, CompositeCleanseFunctionConverter::convert);
    }

    private static CompositeCleanseFunctionRO convert(CompositeCleanseFunctionSource source) {

        CompositeCleanseFunctionRO target = new CompositeCleanseFunctionRO();

        convert(source, target);

        target.setLogic(convertLogic(source.getLogic()));
        return target;
    }

    /**
     * Convert logic.
     *
     * @param source
     *            the source
     * @return the composite function logic
     */
    private static CompositeFunctionLogicRO convertLogic(CompositeCleanseFunctionLogic source) {

        if (source == null) {
            return null;
        }

        CompositeFunctionLogicRO target = new CompositeFunctionLogicRO();
        for (CompositeCleanseFunctionTransition nodeLink : source.getTransitions()) {
            target.addLink(convertTransition(nodeLink));
        }

        for (CompositeCleanseFunctionNode node : source.getNodes()) {
            target.addNode(convertNode(node));
        }

        return target;
    }

    /**
     * Convert node.
     *
     * @param source
     *            the source
     * @return the CF node
     */
    private static CompositeFunctionNodeRO convertNode(CompositeCleanseFunctionNode source) {

        if (source == null) {
            return null;
        }

        CompositeFunctionNodeRO target = new CompositeFunctionNodeRO();
        target.setFunctionName(source.getFunctionName());
        target.setConstant(CONSTANT_CONVERTER.to(source.getConstant()));
        target.setNodeId(source.getNodeId() == null ? null : source.getNodeId().toString());
        target.setNodeType(source.getNodeType() == null ? null : source.getNodeType().name());

        return target;
    }

    /**
     * Convert node link.
     *
     * @param source
     *            the source
     * @return the link
     */
    private static CompositeFunctionTransitionRO convertTransition(CompositeCleanseFunctionTransition source) {

        if (source == null) {
            return null;
        }

        CompositeFunctionTransitionRO target = new CompositeFunctionTransitionRO();
        target.setFromNodeId(source.getFromNodeId() == null ? null : source.getFromNodeId().toString());
        target.setFromPort(source.getFromPort());
        target.setToNodeId(source.getToNodeId() == null ? null : source.getToNodeId().toString());
        target.setToPort(source.getToPort());
        target.setToPortType(source.getToPortType() == null ? null : source.getToPortType().name());
        target.setFromPortType(source.getFromPortType() == null ? null : source.getFromPortType().name());

        return target;
    }

    private static CompositeCleanseFunctionSource convert(CompositeCleanseFunctionRO input) {

        CompositeCleanseFunctionSource target = new CompositeCleanseFunctionSource();

        convert(input, target);

        target.withLogic(convertLogic(input.getLogic()));

// TODO : Fix this. Check and report cyclic routes.
//        CompositeFunctionMDAGRep mdagRep = CFUtils.convertToGraph(cleanseFunctionDef);
//        // 1. validate input, search for cycles
//        List<List<CompositeCleanseFunctionNode>> cycles = CFUtils.findCycles(mdagRep);
//
//        CFCompositeResponse response = new CFCompositeResponse();
//        // 2. if cycles found return error status and list with cycles
//        if (CollectionUtils.isNotEmpty(cycles)) {
//            response.setCycles(CompositeCFConverter.convert(cycles, mdagRep));
//            response.setStatus(CFSaveStatus.ERROR);
//            return Response.accepted(response).build();
//        }

        return target;
    }

    /**
     * Convert logic.
     *
     * @param source
     *            the source
     * @return the composite function logic
     */
    private static CompositeCleanseFunctionLogic convertLogic(CompositeFunctionLogicRO source) {

        if (source == null) {
            return null;
        }

        return new CompositeCleanseFunctionLogic()
                .withTransitions(source.getTransitions().stream()
                    .map(CompositeCleanseFunctionConverter::convertTransition)
                    .collect(Collectors.toList()))
                .withNodes(source.getNodes().stream()
                    .map(CompositeCleanseFunctionConverter::convertNode)
                    .collect(Collectors.toList()));
    }

    /**
     * Convert node.
     *
     * @param source
     *            the source
     * @return the CF node
     */
    private static CompositeCleanseFunctionNode convertNode(CompositeFunctionNodeRO source) {

        if (source == null) {
            return null;
        }

        return new CompositeCleanseFunctionNode()
            .withFunctionName(source.getFunctionName())
            .withNodeId(source.getNodeId() == null ? null : Integer.valueOf(source.getNodeId()))
            .withNodeType(source.getNodeType() == null ? null : CompositeFunctionNodeType.valueOf(source.getNodeType()))
            .withConstant(CONSTANT_CONVERTER.from(source.getConstant()));
    }

    /**
     * Convert node link.
     *
     * @param source
     *            the source
     * @return the link
     */
    private static CompositeCleanseFunctionTransition convertTransition(CompositeFunctionTransitionRO source) {

        if (source == null) {
            return null;
        }

        return new CompositeCleanseFunctionTransition()
                .withFromNodeId(source.getFromNodeId() == null ? null : Integer.valueOf(source.getFromNodeId()))
                .withFromPort(source.getFromPort())
                .withToNodeId(source.getToNodeId() == null ? null : Integer.valueOf(source.getToNodeId()))
                .withToPort(source.getToPort())
                .withToPortType(source.getToPortType() == null ? null : CompositeFunctionNodeType.valueOf(source.getToPortType()))
                .withFromPortType(source.getFromPortType() == null ? null : CompositeFunctionNodeType.valueOf(source.getFromPortType()));
    }
}
